深度之眼Pytorch打卡(五):Pytorch计算图(动态图与静态图)与自动求导tensor.backgrad()

您所在的位置:网站首页 pytorch 动态大小 深度之眼Pytorch打卡(五):Pytorch计算图(动态图与静态图)与自动求导tensor.backgrad()

深度之眼Pytorch打卡(五):Pytorch计算图(动态图与静态图)与自动求导tensor.backgrad()

2023-07-27 04:17| 来源: 网络整理| 查看: 265

前言

   算法的实现过程就是张量进行各种运算的过程,而计算图(Computational Graph)就是记录这些运算过程的有向无环图,比如前向传播时输入张量经过加、减、乘、除得到输出张量,那么计算图就会记录输入输出张量、加减乘除运算和一些中间变量,这是进行反向传播的前提。自动求导是很重要的方法,有这样的机制,可以让我们在设计模型的时候避免去写繁琐的梯度计算代码。本笔记的框架主要来源于深度之眼,并作了一些相关的拓展,拓展内容主要源自对torch文档的翻译理解和一些介绍tensorflow和Pytorch的网站。

静态图与动态图

   计算图分为静态图和动态图。静态图,顾名思义就是图是确定的,即整个运算过程预先定义好了,然后再次运行的过程中只运算而不再搭建计算图,看起来就是数据在规定的图中流动。动态图,就是计算图是动态生成的,即边运算边生成计算图,是一个不断完成的过程,可能每运行一行代码都会拓展计算图。动态图便于调试、灵活,静态图速度要高效一些,但是不能改变数据流向。

   计算图是静态的深度学习框架,比较典型的就是Tensorflow。其也是因为张量在预先定义的图中流动而得名tensor flow,如图1所示(图片来自一介绍tensorflow的国外网站)。 在这里插入图片描述

图1.tensor在计算图中flow

   Pytorch的计算图就是动态的,如图2所示(图片来自github上的Pytorch),几乎每进行一次运算都会拓展原先的计算图。最后生成完成,进行反向传播,当反向传播完成,计算图默认会被清除,即进行前向传播时记录的计算过程会被释放掉。所以,默认情况下,进行一次前向传播后最多只能用生成的计算图进行一次反向传播。 在这里插入图片描述

图2.Pytorch动态生成计算图 自动求导 张量属性

   回顾一下在笔记:​​深度之眼Pytorch打卡(二):Pytorch张量与张量的创建中梳理的张量的几个与自动求导有关的属性。所有属性如图3所示。 ​​在这里插入图片描述

图3.tensor属性

   data: 用于存放tensor,是数据本体。

   grad: 存放data的梯度值。

   grad_fn: 记录生成该张量时所用的方法(函数——如乘法:grad_fn = < MulBackward0 >,加法:grad_fn = < AddBackward0 >),用于反向传播自动求梯度时选择求导方式。

   requires_grad: 指示该是否需要梯度,对于需要求导的tensor,其requires_grad属性必须为True,这样自动求导时才会对该张量求梯度。自己定义的tensor的requires_grad属性默认为False。神经网络层中的权值w的tensor的requires_grad属性默认为True,求导对象与被求导对象之间的中间变量也默认True。

   is_ leaf : 指示是否是叶子结点(张量),用户创建的结点称为叶子结点(张量)如X与 W,运算生成的则不是。梯度计算时的值来源于叶子节点,即求各节点的梯度值时,都要带入叶子结点的值进表达式,所以在一次反向传播完成前叶子结点的值不能在原地址上被修改(In-place operation)(修改会报错)。is_leaf为True的时候是叶子节点。叶子节点在经过反向传播之后梯度值能够得以保留,非叶子节点的梯度值则为:None,中间虽然算过,但是最后默认都释放了。

   代码中的计算图(计算过程)是:x = a + b;y = x.w;z = 0.5*y^2;m = (z - 1)/2;n = m - 0.1;p = sin(n);

import torch a = torch.eye(4, 3) b = torch.full_like(a, 1) x = torch.add(a, 1, b) w = torch.randn(len(x[0]), 1, requires_grad=True) y = torch.mm(x, w) z = 0.5*y**2 m = (z - 1)/2 n = m - 0.1 p = torch.sin(n) n.retain_grad() p.backward(torch.ones_like(y)) print(m.requires_grad)# 它的 requires_grad 默认True print(a.is_leaf, b.is_leaf, x.is_leaf, w.is_leaf, y.is_leaf, z.is_leaf, m.is_leaf, n.is_leaf, p.is_leaf) print(x, '\n', y, '\n', z, '\n', m, '\n', n, '\n', p) print('grad_w:', w.grad, '\ngrad_y:', y.grad, '\ngrad_n:', n.grad)

   当tensor执行backward()操作时,如果没有元素需要求梯度,即requires_grad=True,那便会出现如下错误。    RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

   当执行反向传播的张量不是标量时,如代码中的张量p,则会出现如下错误,p.backward()默认p是标量,如果p是向量,则需要用与p形状一致的权值张量作为参数,如p.backward(torch.ones_like(y)))    RuntimeError: grad can be implicitly created only for scalar outputs

   当有张量需要求导时,叶子张量才是有意义的,如果没有张量需要求导,你会发现所有的张量is_leaf都为True,完成反向传播之后只有叶子节点的梯度值能够保留,其他的非叶子节点如果想保留,则在反向传播之前用retain_grad()。另外,从第一个需要求导的张量开始,grad_fn就开始记录到输出过程中所有的运算方法,这是进行反向传播求取梯度的前提。

True True True True True False False False False False tensor([[2., 1., 1.], [1., 2., 1.], [1., 1., 2.], [1., 1., 1.]]) tensor([[3.1440], [2.4286], [0.0188], [1.3979]], grad_fn=) tensor([[4.9424e+00], [2.9490e+00], [1.7729e-04], [9.7700e-01]], grad_fn=) tensor([[ 1.9712], [ 0.9745], [-0.4999], [-0.0115]], grad_fn=) tensor([[ 1.8712], [ 0.8745], [-0.5999], [-0.1115]], grad_fn=) tensor([[ 0.9552], [ 0.7672], [-0.5646], [-0.1113]], grad_fn=) grad_w: tensor([[0.7480], [0.9345], [0.9587]]) grad_y: None grad_n: tensor([[0.8277], [0.8663], [0.8736], [0.8397]]) 自动求导

 torch.autograd.backward()——自动求取该张量与先前所有需要求梯度的张量间的梯度

torch.autograd.backward(tensors, grad_tensors=None, retain_graph=None, create_graph=False, grad_variables=None)

  生成完成计算图,即完成前向传播后,就可以根据计算图,通过自动求导方法,得到每一个需要求取梯度的张量的梯度,然后结合梯度下降等优化器,就可以更新参数了,可见这是优化模型时的核心操作。

  tensors: 被求导的张量,默认是标量。   grad_tensors: 当被求导的张量不是标量时,需要设置该参数,作用是对向量进行加权和,进而变成一个标量。在上述代码中,用tensor.backward(torch.ones_like(tensor)),表示tensor中每个元素的权重都是1,即影响相同。如果想要强调某个元素,可以对其设置较高的权重。   retain_graph: 动态图,进行完反向传播计算图就会默认释放,如果想要保留,则设置该参数为True。   create_graph: 记录求导过程中的函数,即生成导数计算图,如果要求高阶导数,需要设置该参数为True。

def backward(self, gradient=None, retain_graph=None, create_graph=False): torch.autograd.backward(self, gradient, retain_graph, create_graph)

  注:上述代码中的tensor.backward()和torch.autograd.backward()基本上就是一个东西。不过当tensor不是标量时,torch.autograd.backward()需要这样的代码:torch.autograd.backward(tensor, grad_tensors= torch.ones_like(tensor))

y.backward(torch.ones_like(y), retain_graph=True) y.backward(torch.ones_like(y))

  如果需要再次使用计算图,则要:retain_graph=True,否则会出现如下错误:   RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time.

  注:梯度不会自动清零,比如上述代码中w的梯度,如果那里是循环操作,那么w的梯度值会一直累加下去,这显然不是我们想看到的。需要用w.grad.zero_()函数手动清零,注意到函数中的下划线,那是只在w所在地址直接进行操作,即In-place operation,叶子节点不允许在反向传播前进行原位操作。

 torch.autograd.grad()——自动计算输出与输入之间的梯度

torch.autograd.grad(outputs, inputs, grad_outputs=None, retain_graph=None, create_graph=False, only_inputs=True, allow_unused=False)

  only_inputs: 好像现在的版本没有使用了,默认是Ture,改成False之后也不能实现文档中所描述的求取其他叶子节点的梯度,反而会有警告:   UserWarning: only_inputs argument is deprecated and is ignored now (defaults to True). To accumulate gradient for other parts of the graph, please use torch.autograd.backward

  outputs: 被求导的张量   inputs: 需要求导的张量,注意梯度直接返回,而不是放到tensor.grad中。

a = torch.full([1, 1], 1, requires_grad=True) b = torch.full([1, 1], 0.5, requires_grad=True) x = a + b y = torch.reciprocal(torch.exp(-x) + 1) # sigmoid 函数 grady1 = torch.autograd.grad(y, a, retain_graph=True) # 保留计算图 print(grady1, a.grad) grady1 = torch.autograd.grad(y, a, only_inputs=False, create_graph=True) print(grady1, a.grad) grady2 = torch.autograd.grad(grady1, a) # 求二阶导数 print(grady2) (tensor([[0.1491]]),) None (tensor([[0.1491]], grad_fn=),) None (tensor([[-0.0947]]),) 参考

  https://pytorch.org/docs/stable/torch.html   https://ai.deepshare.net/detail/p_5df0ad9a09d37_qYqVmt85/6   https://johncarlosbaez.wordpress.com/2016/06/05/programming-with-data-flow-graphs/   https://github.com/pytorch/pytorch/blob/master/docs/source/_static/img/dynamic_graph.gif



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3